using System;
using System.Collections;

using Utility;

namespace MapsJavascript {

	public class GoogleMapsFactory : JavascriptFactory {
		public override JavascriptObject createControl(Maps.Control control) {
			return new GoogleMaps.GControl(control);
		}
		public override JavascriptObject createLatLng(Maps.LatLng point) {
			return new GoogleMaps.GLatLng(point);
		}
		public override JavascriptObject createOverlay(Maps.Map map, Maps.Overlay overlay) {
			return new GoogleMaps.GOverlay(map, overlay);
		}
		public override JavascriptObject createIcon(Maps.Icon icon) {
			return new GoogleMaps.GIcon(icon);
		}
		public override JavascriptObject createInfoWindowTab(Maps.InfoWindowTab tab) {
			return new GoogleMaps.GInfoWindowTab(tab);
		}
		public override JavascriptFunction createClientGeocoderFunc(string address, string geocoder, string function) {
			return new GoogleMaps.GClientGeocoder(address, geocoder, function);
		}
		public override JavascriptObject createMapOverlays(Maps.Map map) {
			return new GoogleMaps.GMapOverlays(map);
		}
		public override JavascriptFunction createMapFunc(Maps.Map map) {
			return new GoogleMaps.GMap2(map);
		}
	}


	namespace GoogleMaps {

		public class GControl : Utility.JavascriptObject {
			public GControl(Maps.Control control) {
				this.control = control;
			}

			Maps.Control control;

			public override void create() {
				if(control is Maps.LargeMapControl) {
					create((Maps.LargeMapControl)control);
				}
				else if(control is Maps.SmallMapControl) {
					create((Maps.SmallMapControl)control);
				}
				else if(control is Maps.SmallZoomControl) {
					create((Maps.SmallZoomControl)control);
				}
				else if(control is Maps.MapTypeControl) {
					create((Maps.MapTypeControl)control);
				}
				else {
					throw new NotImplementedException("Invalid control type.  Custom controls are not yet implemented in the c# google maps api!");
				}
			}

			private void create(Maps.LargeMapControl control) {
				javascript("new GLargeMapControl()");
			}
			private void create(Maps.SmallMapControl control) {
				javascript("new GSmallMapControl()");
			}
			private void create(Maps.SmallZoomControl control) {
				javascript("new GSmallZoomControl()");
			}
			private void create(Maps.MapTypeControl control) {
				javascript("new GMapTypeControl()");
			}

		}

		public class GLatLng : Utility.JavascriptObject {
			public GLatLng(Maps.LatLng point) {
				this.point = point;
			}

			Maps.LatLng point;

			public override void create() {
				javascript("new GLatLng(" + point + ")");
			}
		}

		public class GOverlay : Utility.JavascriptObject {
			public GOverlay(Maps.Map map, Maps.Overlay overlay) {
				this.map = map;
				this.overlay = overlay;
			}

			Maps.Map map;
			Maps.Overlay overlay;

			public override void create() {
				if(overlay.isValid()) {
					if(overlay is Maps.Marker) {
						create((Maps.Marker)overlay);
					}
					else {
						throw new Exception("Couldn't recognize overlay type!");
					}
				}
				else {
					throw new Exception("Invalid overlay (is point null?)! " + overlay);
				}
			}

			private void create(Maps.Marker overlay) {
				Maps.MarkerOptions opts = overlay.getOpts();
				string optsString = "{nullvar: null";
				if(opts.icon != null) {
					GIcon icon = new GIcon(opts.icon);
					javascript(icon.ToString());
					optsString += ",icon: " + icon.getName();
					jsnullify(icon.getName());
				}
				if(opts.dragCrossMove) optsString += ", dragCrossMove: true";
				if(opts.title != "") optsString += ", title: " + opts.title;
				if(!opts.clickable) optsString += ", clickable: false";
				if(opts.draggable) optsString += ", draggable: true";
				if(opts.bouncy) optsString += ", bouncy: true";
				if(opts.bounceGravity != 1) optsString += ", bounceGravity: " + opts.bounceGravity;
				optsString += "}";

				javascript("var " + getName() + " = new GMarker(" + new GLatLng(overlay.getPoint()) +  "," + optsString + ");\n");

				IDictionaryEnumerator iter = overlay.getEvents().GetEnumerator();
				while(iter.MoveNext()) {
					string js = iter.Value.ToString().Replace("--self--", getName());
					javascript("GEvent.addListener(" + getName() + ", '" + iter.Key + "', " + js + ");\n");
				}

				if(overlay.getCustomInfoWindow() != null) {
					GOverlay window = new GOverlay(map, overlay.getCustomInfoWindow());
					javascript("" + window);
					javascript(getName() + ".customWindow = " + window.getName() + ";");

					javascript("GEvent.addListener(" + getName() + ", 'click', function() {");
					javascript(window.getName() + ".state('default');" + map.getContainer() + ".addOverlay(" + window.getName() + ");");
					javascript("});");
				}
				else if(overlay.getInfoWindowTabs().Count > 0) {
					javascript("GEvent.addListener(" + getName() + ", 'click', function() { " + getName() + ".openInfoWindowTabsHtml(" + createTabArray(overlay.getInfoWindowTabs()) + "); });\n");
				}
				else if(overlay.getInfoWindowContent() != null) {
					javascript("GEvent.addListener(" + getName() + ", 'click', function() { " + getName() + ".openInfoWindowHtml('" + Utility.Javascript.escape(overlay.getInfoWindowContent().Replace("\n", "")) + "'); });");
				}
			}

			protected string createTabArray(ArrayList tabs) {
				string ret = "new Array(";
				bool first = true;
				foreach(Maps.InfoWindowTab tab in tabs) {
					if(!first) ret += ",";
					ret += new GInfoWindowTab(tab);
					first = false;
				}
				ret += ")";
				return ret;
			}

		}

		public class GIcon : Utility.JavascriptObject {
			public GIcon(Maps.Icon icon) {
				this.icon = icon;
			}

			Maps.Icon icon;

			public override void create() {
				javascript("var " + getName() + " = new GIcon(G_DEFAULT_ICON);");

				if(icon.iconSize != null) javascript(getName() + ".iconSize = new GSize(" + icon.iconSize + ");");
				if(icon.shadow != null) javascript(getName() + ".shadow = '" + icon.shadow + "';");
				if(icon.shadowSize != null) javascript(getName() + ".shadowSize = new GSize(" + icon.shadowSize + ");");
				if(icon.iconAnchor != null) javascript(getName() + ".iconAnchor = new GPoint(" + icon.iconAnchor + ");");
				if(icon.infoWindowAnchor != null) javascript(getName() + ".infoWindowAnchor = new GPoint(" + icon.infoWindowAnchor + ");");
				if(icon.printImage != null) javascript(getName() + ".printImage = '" + icon.printImage + "';");
				if(icon.mozPrintImage != null) javascript(getName() + ".mozPrintImage = '" + icon.mozPrintImage + "';");
				if(icon.printShadow != null) javascript(getName() + ".printShadow = '" + icon.printShadow + "';");
				if(icon.transparent != null) javascript(getName() + ".transparent = '" + icon.transparent + "';");
				if(icon.imageMap != null) javascript(getName() + ".imageMap = new Array(" + Util.implode(icon.imageMap) + ");");
				if(icon.image != null) javascript(getName() + ".image = '" + icon.image + "';");
			}
		}

		public class GInfoWindowTab : Utility.JavascriptObject {
			public GInfoWindowTab(Maps.InfoWindowTab tab) {
				this.tab = tab;
			}

			Maps.InfoWindowTab tab;

			public override void create() {
				javascript("new GInfoWindowTab('" + tab.getLabel() + "', '" + Utility.Javascript.escape(tab.getContent().Replace("\n", "")) + "')");
			}
		}

		public class GClientGeocoder : Utility.JavascriptFunction {
			public GClientGeocoder(string address, string geocoder, string function) {	
				this.address = address;
				this.geocoder = geocoder;
				this.function = function;
			}

			private string address;
			private string geocoder;
			private string function;

			public override void create() {
				javascript(geocoder + ".getLatLng('" + Utility.Javascript.escape(address) + "', " + function + ");");
			}
		}

		public class GMapOverlays : Utility.JavascriptObject {
			public GMapOverlays(Maps.Map map) {
				this.map = map;
			}

			private Maps.Map map;

			public override void create() {
				string mapVar = map.getContainer();

				javascript("if(GBrowserIsCompatible()) {");

				javascript("/*	Begin code to populate map */\n");
				javascript(mapVar + ".markerMgr.clearOverlays();\n");

				// Create overlays
				foreach(Maps.Overlay overlay in map.getOverlays()) {
					if(overlay.isValid()) {
						GOverlay overlayJS = new GOverlay(map, overlay);
						javascript("" + overlayJS);
						javascript(mapVar + ".markerMgr.addMarker(" + overlayJS.getName() + ");\n");
					}
				}

				if(map.getCenter() == null && map.getZoom() == 0) {
					javascript(mapVar + ".markerMgr.resetView();\n");
				}

				javascript(mapVar + ".markerMgr.refresh();\n");
				javascript("/*	End code to populate map */\n");

				javascript("}");
			}
		}


		public class GMap2 : Utility.JavascriptFunction {
			public GMap2(Maps.Map map){
				this.map = map;
			}

			private Maps.Map map;

			public override void create() {
				string mapVar = map.getContainer();

				javascript("if(GBrowserIsCompatible()) {");

				javascript("var defaultCenter = new GLatLng(40.0, -100.0);");
				javascript("var defaultZoom = 4;");

				javascript("if(!" + mapVar + ") {");
				javascript("var opts = {nullvar: null");
				if(map.getMapTypes().Count > 0) {
					javascript(", mapTypes: new Array('" + Util.implode(map.getMapTypes()) + "')");
				}
				else {
					javascript(", mapTypes: G_DEFAULT_MAP_TYPES");
				}
				if(map.getSize() != null) javascript(", size: new GSize(" + map.getSize().width + "," + map.getSize().height + ")");
				if(map.getDraggableCursor() != "") javascript(", draggableCursor: " + map.getDraggableCursor());
				if(map.getDraggingCursor() != "") javascript(", draggingCursor: " + map.getDraggingCursor());
				javascript("};");
				javascript(mapVar + " = new GMap2($('" + map.getContainer() + "'), opts);");

				// Create controls
				foreach(Maps.Control control in map.getControls()) {
					javascript(mapVar + ".addControl(" + new GControl(control) + ");");
				}

				/* Handle wheel scrolling
				javascript("function onMouseWheel(e) {");
				javascript(" var zoom = map.getZoom() + Event.wheel(e);");
				javascript(" if(zoom < 0) zoom = 0;  if(zoom > 17) zoom = 17;");
				javascript(" map.setCenter(map.getCenter(), zoom);");
				javascript("}");
				javascript("Event.observe($('map'), 'mousewheel', onMouseWheel);");
				javascript("Event.observe($('map'), 'DOMMouseScroll', onMouseWheel);");
				*/

				javascript("}");

				javascript(mapVar + ".setCenter(" + (map.getCenter() != null ? "new GLatLng(" + map.getCenter() + ")" : "defaultCenter") + ", " + (map.getZoom() != 0 ? map.getZoom().ToString() : "defaultZoom") + ");");

				if(!map.draggingEnabled()) javascript(mapVar + ".disableDragging();");
				if(!map.infoWindowEnabled()) javascript(mapVar + ".disableInfoWindow();");
				if(map.doubleClickZoomEnabled()) javascript(mapVar + ".enableDoubleClickZoom();");
				if(map.continuousZoomEnabled()) javascript(mapVar + ".enableContinuousZoom();");

				GMapOverlays overlayMgr = new GMapOverlays(map);
				javascript("var " + overlayMgr.getName() + " = new markerManager(" + mapVar + ");");
				javascript(mapVar + ".markerMgr = " + overlayMgr.getName() + ";");
				javascript("GEvent.addListener(" + mapVar + ", 'moveend', " + overlayMgr.getName() + ".onMoveEnd.bind(" + overlayMgr.getName() + "));");
				javascript("GEvent.addListener(" + mapVar + ", 'zoomend', " + overlayMgr.getName() + ".onZoomEnd.bind(" + overlayMgr.getName() + "));");
				javascript("" + overlayMgr);
				jsnullify(overlayMgr.getName());

				javascript("}");
			}

			public override string globalVariables() {
				return "var " + map.getContainer() + ";";
			}
		}

		/*	If we ever want to have different markers at different zoom level, we should look more into
		 *  this marker manager.  The only bug right now is that it doesn't properly clear the map.
		 */
		public class GMarkerManager : Utility.JavascriptFunction {
			public GMarkerManager(Maps.MarkerManager mgr) {
				this.mgr = mgr;
				this.map = map;
			}
			public GMarkerManager(Maps.MarkerManager mgr, Maps.Map map) {
				this.mgr = mgr;
				this.map = map;
			}

			private Maps.MarkerManager mgr;
			private Maps.Map map;

			public override void create() {
				string mapVar = map.getContainer();
				javascript("var markMgr = new GMarkerManager(" + mapVar + ");");

				ArrayList batches = mgr.getMarkerBatches();
				foreach(Maps.MarkerManager.MarkerBatch b in batches) {
					string opts = "";
					opts += b.minZoom;
					if(b.maxZoom != -1) opts += ", " + b.maxZoom;

					foreach(Maps.Marker m in b.markers) {
						if(m.isValid()) {
							GOverlay mJS = new GOverlay(map, m);
							javascript(mJS.ToString());
							javascript("markMgr.addMarker(" + mJS.getName() + "," + opts + ");");
							javascript("resizeBounds(" + mJS.getName() + ".getPoint());");
						}
					}
				}

				javascript("markMgr.refresh();");
			}

		}
	}
}
